<?php
/**
 * <https://y.st./>
 * Copyright © 2017 Alex Yst <mailto:copyright@y.st>
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org./licenses/>.
**/

$xhtml = array(
	'title' => 'DrawTextPanel.java',
	'subtitle' => 'Written in <span title="Programming 2">CS 1103</span> of <a href="http://www.uopeople.edu/">University of the People</a>, finalised on 2017-07-26',
	'copyright year' => '2017',
	'body' => <<<END
<h2>Source code</h2>
<blockquote>
<pre><code>/* 
 * Copyright (C) 2017 Alex Yst
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see &lt;http://www.gnu.org/licenses/&gt;.
 */
package textcollage;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.JColorChooser;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.KeyStroke;

/**
 * A panel that contains a large drawing area where strings
 * can be drawn.  The strings are represented by objects of
 * type DrawTextItem.  An input box under the panel allows
 * the user to specify what string will be drawn when the
 * user clicks on the drawing area.
 */
public class DrawTextPanel extends JPanel  {
/**
 * The JPanel class seems to want this version integer implemented in child
 * classes. As far as I know, it&apos;s not used by this program, but adding it gets
 * Eclipse to stop complaining about the fact that it&apos;s missing.
 */
	private static final long serialVersionUID = 0;
/**
 * An array of objects representing strings to draw
 */
	private ArrayList&lt;DrawTextItem&gt; theString = new ArrayList&lt;DrawTextItem&gt;();
	private java.awt.Image image;
	private Color currentTextColor = Color.BLACK;  // Colour applied to new strings.
	private Canvas canvas;  // the drawing area.
	private JTextField input;  // where the user inputs the string that will be added to the canvas
	private SimpleFileChooser fileChooser;  // for letting the user select files
	private JMenuBar menuBar; // a menu bar with command that affect this panel
	private MenuHandler menuHandler; // a listener that responds whenever the user selects a menu command
	private JMenuItem undoMenuItem;  // the &quot;Remove Item&quot; command from the edit menu
/**
 * An object of type Canvas is used for the drawing area.
 * The canvas simply displays all the DrawTextItems that
 * are stored in the ArrayList, strings.
 */
	private class Canvas extends JPanel {
/**
 * The JPanel class seems to want this version integer implemented in child
 * classes. As far as I know, it&apos;s not used by this program, but adding it gets
 * Eclipse to stop complaining about the fact that it&apos;s missing.
 */
	private static final long serialVersionUID = 0;
		Canvas() {
			setPreferredSize( new Dimension(800,600) );
			setBackground(Color.LIGHT_GRAY);
			setFont( new Font( &quot;Serif&quot;, Font.BOLD, 24 ));
		}
		protected void paintComponent(Graphics g) {
			super.paintComponent(g);
			((Graphics2D)g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
					RenderingHints.VALUE_ANTIALIAS_ON);
			if(image != null) {
				g.drawImage(image, 0, 0, 800, 600, this);
			}
			for(DrawTextItem item : theString) {
				item.draw(g);
			}
		}
	}
	
/**
 * An object of type MenuHandler is registered as the ActionListener
 * for all the commands in the menu bar.  The MenuHandler object
 * simply calls doMenuCommand() when the user selects a command
 * from the menu.
 */
	private class MenuHandler implements ActionListener {
		public void actionPerformed(ActionEvent evt) {
			doMenuCommand( evt.getActionCommand());
		}
	}
/**
 * Creates a DrawTextPanel.  The panel has a large drawing area and
 * a text input box where the user can specify a string.  When the
 * user clicks the drawing area, the string is added to the drawing
 * area at the point where the user clicked.
 */
	public DrawTextPanel() {
		fileChooser = new SimpleFileChooser();
		undoMenuItem = new JMenuItem(&quot;Remove Item&quot;);
		undoMenuItem.setEnabled(false);
		menuHandler = new MenuHandler();
		setLayout(new BorderLayout(3,3));
		setBackground(Color.BLACK);
		setBorder(BorderFactory.createLineBorder(Color.BLACK, 2));
		canvas = new Canvas();
		add(canvas, BorderLayout.CENTER);
		JPanel bottom = new JPanel();
		bottom.add(new JLabel(&quot;Text to add: &quot;));
		input = new JTextField(&quot;Hello World!&quot;, 40);
		bottom.add(input);
		add(bottom, BorderLayout.SOUTH);
		canvas.addMouseListener( new MouseAdapter() {
			public void mousePressed(MouseEvent e) {
				doMousePress( e );
			}
		} );
	}
/**
 * This method is called when the user clicks the drawing area.
 * A new string is added to the drawing area.  The center of
 * the string is at the point where the user clicked.
 * @param e the mouse event that was generated when the user clicked
 */
	public void doMousePress( MouseEvent e ) {
		String text = input.getText().trim();
		if (text.length() == 0) {
			input.setText(&quot;Hello World!&quot;);
			text = &quot;Hello World!&quot;;
		}
		DrawTextItem s = new DrawTextItem( text, e.getX(), e.getY() );
		s.setTextColor(currentTextColor);  // Default is null, meaning default color of the canvas (black).
		
//   SOME OTHER OPTIONS THAT CAN BE APPLIED TO TEXT ITEMS:
//		s.setFont( new Font( &quot;Serif&quot;, Font.ITALIC + Font.BOLD, 12 ));  // Default is null, meaning font of canvas.
//		s.setMagnification(3);  // Default is 1, meaning no magnification.
//		s.setBorder(true);  // Default is false, meaning don&apos;t draw a border.
//		s.setRotationAngle(25);  // Default is 0, meaning no rotation.
//		s.setTextTransparency(0.3); // Default is 0, meaning text is not at all transparent.
//		s.setBackground(Color.BLUE);  // Default is null, meaning don&apos;t draw a background area.
//		s.setBackgroundTransparency(0.7);  // Default is 0, meaning background is not transparent.
		theString.add(s);
		undoMenuItem.setEnabled(true);
		canvas.repaint();
	}
/**
 * Returns a menu bar containing commands that affect this panel.  The menu
 * bar is meant to appear in the same window that contains this panel.
 */
	public JMenuBar getMenuBar() {
		if (menuBar == null) {
			menuBar = new JMenuBar();

			String commandKey; // for making keyboard accelerators for menu commands
			if (System.getProperty(&quot;mrj.version&quot;) == null)
				commandKey = &quot;control &quot;;  // command key for non-Mac OS
			else
				commandKey = &quot;meta &quot;;  // command key for Mac OS

			JMenu fileMenu = new JMenu(&quot;File&quot;);
			menuBar.add(fileMenu);
			JMenuItem saveItem = new JMenuItem(&quot;Save...&quot;);
			saveItem.setAccelerator(KeyStroke.getKeyStroke(commandKey + &quot;N&quot;));
			saveItem.addActionListener(menuHandler);
			fileMenu.add(saveItem);
			JMenuItem openItem = new JMenuItem(&quot;Open...&quot;);
			openItem.setAccelerator(KeyStroke.getKeyStroke(commandKey + &quot;O&quot;));
			openItem.addActionListener(menuHandler);
			fileMenu.add(openItem);
			fileMenu.addSeparator();
			JMenuItem saveImageItem = new JMenuItem(&quot;Save Image...&quot;);
			saveImageItem.setAccelerator(KeyStroke.getKeyStroke(commandKey + &quot;S&quot;));
			saveImageItem.addActionListener(menuHandler);
			fileMenu.add(saveImageItem);

			JMenu editMenu = new JMenu(&quot;Edit&quot;);
			menuBar.add(editMenu);
			undoMenuItem.addActionListener(menuHandler); // undoItem was created in the constructor
			undoMenuItem.setAccelerator(KeyStroke.getKeyStroke(commandKey + &quot;Z&quot;));
			editMenu.add(undoMenuItem);
			editMenu.addSeparator();
			JMenuItem clearItem = new JMenuItem(&quot;Clear&quot;);
			clearItem.setAccelerator(KeyStroke.getKeyStroke(commandKey + &quot;E&quot;));
			clearItem.addActionListener(menuHandler);
			editMenu.add(clearItem);

			JMenu optionsMenu = new JMenu(&quot;Options&quot;);
			menuBar.add(optionsMenu);
			JMenuItem colorItem = new JMenuItem(&quot;Set Text Color...&quot;);
			colorItem.setAccelerator(KeyStroke.getKeyStroke(commandKey + &quot;T&quot;));
			colorItem.addActionListener(menuHandler);
			optionsMenu.add(colorItem);
			JMenuItem bgColorItem = new JMenuItem(&quot;Set Background Color...&quot;);
			bgColorItem.setAccelerator(KeyStroke.getKeyStroke(commandKey + &quot;B&quot;));
			bgColorItem.addActionListener(menuHandler);
			optionsMenu.add(bgColorItem);

			JMenuItem bgImageItem = new JMenuItem(&quot;Set Background Image...&quot;);
			bgImageItem.setAccelerator(KeyStroke.getKeyStroke(commandKey + &quot;I&quot;));
			bgImageItem.addActionListener(menuHandler);
			optionsMenu.add(bgImageItem);
		}
		return menuBar;
	}
/**
 * Carry out one of the commands from the menu bar.
 * @param command the text of the menu command.
 */
	private void doMenuCommand(String command) {
		if (command.equals(&quot;Save...&quot;)) { // save all the string info to a file
			File file = fileChooser.getOutputFile(this, &quot;Select Text File Name&quot;, &quot;textimage.txt&quot;);
			if(file == null) {
				JOptionPane.showMessageDialog(this, &quot;ERROR: No file name chosen; file save aborted.&quot;);
			} else {
				try {
					java.io.PrintWriter f = new java.io.PrintWriter(file);
					Color b = canvas.getBackground();
					f.print(b.getRed());
					f.print(&apos;\t&apos;);
					f.print(b.getGreen());
					f.print(&apos;\t&apos;);
					f.println(b.getBlue());
					for(DrawTextItem item : theString) {
						Color c = item.getTextColor();
						f.print(c.getRed());
						f.print(&apos;\t&apos;);
						f.print(c.getGreen());
						f.print(&apos;\t&apos;);
						f.print(c.getBlue());
						f.print(&apos;\t&apos;);
						f.print(item.getX());
						f.print(&apos;\t&apos;);
						f.print(item.getY());
						f.print(&apos;\t&apos;);
						f.println(item.getString());
					}
					f.close();
				} catch(java.io.FileNotFoundException e) {
					JOptionPane.showMessageDialog(this, &quot;ERROR: &quot;+e.getMessage());
				}
			}
		}
		else if (command.equals(&quot;Open...&quot;)) { // read a previously saved file, and reconstruct the list of strings
			File file = fileChooser.getInputFile(this, &quot;Select Text File&quot;);
			if(file == null) {
				JOptionPane.showMessageDialog(this, &quot;ERROR: No file chosen; file load aborted.&quot;);
			} else {
				try {
					java.util.Scanner f = new java.util.Scanner(file);
// I tried not creating an unused object, but Eclipse wouldn&apos;t stop complaining
// that the variable might not be initialised. This was an especially stupid
// complaint when I set up a check, `if(canvasColour != null)`. Eclipse
// complained both in the check, which was trying to see if it had been set,
// and in the block, which would only be executed if it *had* been set. The
// only way I could figure out how to get the code right while getting Eclipse
// to stop complaining was to construct this useless dummy object that will
// under no circumstances actually get used.
					Color canvasColour = new Color(0,0,0);
					boolean canvasColourConstructed = false;
					ArrayList&lt;DrawTextItem&gt; tempTheString = new ArrayList&lt;DrawTextItem&gt;();
					String[] values = f.nextLine().split(&quot;\t&quot;);
					if(values.length &gt; 2) {
						canvasColour = new Color(
							Integer.parseInt(values[0]),
							Integer.parseInt(values[1]),
							Integer.parseInt(values[2])
						);
						canvasColourConstructed = true;
					}
					while(f.hasNext()) {
						values = f.nextLine().split(&quot;\t&quot;);
						if(values.length &gt; 5) { // The bare minimum number of values
							Color c = new Color(
								Integer.parseInt(values[0]),
								Integer.parseInt(values[1]),
								Integer.parseInt(values[2])
							);
							DrawTextItem item = new DrawTextItem(
								values[5],
								Integer.parseInt(values[3]),
								Integer.parseInt(values[4])
							);
							item.setTextColor(c);
							tempTheString.add(item);
						}
					}
					f.close();
					theString = tempTheString;
					if(canvasColourConstructed) {
						canvas.setBackground(canvasColour);
					}
					canvas.repaint();
				} catch(java.io.FileNotFoundException e) {
					JOptionPane.showMessageDialog(this, &quot;ERROR: File not found. (&quot;+e.getMessage()+&quot;)&quot;);
				} catch(Exception e) {
					JOptionPane.showMessageDialog(this, &quot;ERROR: File format not supported. (&quot;+e.getMessage()+&quot;)&quot;);
				}
			}
		}
		else if (command.equals(&quot;Clear&quot;)) {
			theString.clear();
			undoMenuItem.setEnabled(false);
			canvas.repaint();
		}
		else if (command.equals(&quot;Remove Item&quot;)) {
			theString.remove(theString.size()-1);
			if(theString.size() == 0) {
				undoMenuItem.setEnabled(false);
			}
			canvas.repaint();
		}
		else if (command.equals(&quot;Set Text Color...&quot;)) {
			Color c = JColorChooser.showDialog(this, &quot;Select Text Color&quot;, currentTextColor);
			if (c != null)
				currentTextColor = c;
		}
		else if (command.equals(&quot;Set Background Color...&quot;)) {
			Color c = JColorChooser.showDialog(this, &quot;Select Background Color&quot;, canvas.getBackground());
			if (c != null) {
				image = null;
				canvas.setBackground(c);
				canvas.repaint();
			}
		}
		else if (command.equals(&quot;Set Background Image...&quot;)) {
			File file = fileChooser.getInputFile(this, &quot;Select Image File&quot;);
			if(file == null) {
				JOptionPane.showMessageDialog(this, &quot;ERROR: No file chosen; file load aborted.&quot;);
			} else {
				String fileType= new javax.activation.MimetypesFileTypeMap().getContentType(file);
				if(fileType.substring(0, 6).equals(&quot;image/&quot;)) {
					try {
						image = ImageIO.read(file);
						canvas.repaint();
					} catch(java.io.FileNotFoundException e) {
						JOptionPane.showMessageDialog(this, &quot;ERROR: File not found. (&quot;+e.getMessage()+&quot;)&quot;);
					} catch(Exception e) {
						JOptionPane.showMessageDialog(this, &quot;ERROR: File format not supported. (&quot;+e.getMessage()+&quot;)&quot;);
					}
				} else {
					JOptionPane.showMessageDialog(this, &quot;ERROR: The selected file does not appear to be an image file.&quot;);
				}
			}
		}
		else if (command.equals(&quot;Save Image...&quot;)) {  // save a PNG image of the drawing area
			File imageFile = fileChooser.getOutputFile(this, &quot;Select Image File Name&quot;, &quot;textimage.png&quot;);
			if (imageFile == null)
				return;
			try {
// Because the image is not available, I will make a new BufferedImage and
// draw the same data to the BufferedImage as is shown in the panel.
// A BufferedImage is an image that is stored in memory, not on the screen.
// There is a convenient method for writing a BufferedImage to a file.
				BufferedImage image = new BufferedImage(canvas.getWidth(),canvas.getHeight(),
						BufferedImage.TYPE_INT_RGB);
				Graphics g = image.getGraphics();
				g.setFont(canvas.getFont());
				canvas.paintComponent(g);  // draws the canvas onto the BufferedImage, not the screen!
				boolean ok = ImageIO.write(image, &quot;PNG&quot;, imageFile); // write to the file
				if (ok == false)
					throw new Exception(&quot;PNG format not supported (this shouldn&apos;t happen!).&quot;);
			}
			catch (Exception e) {
				JOptionPane.showMessageDialog(this, 
						&quot;Sorry, an error occurred while trying to save the image:\n&quot; + e);
			}
		}
	}
/**
 * It was a slight pain needing to work with two files at once. I needed to
 * work with this file to edit it, but the TextCollage.java file to run the
 * program to test. Setting up this main() method to call the main() method of
 * the other file&apos;s class was just a shortcut so I only had to work with this
 * one file.
 * 
 * @param arguments Command line arguments
 */
	public static void main(String[] arguments) {
		TextCollage.main(arguments);
	}
}</code></pre>
</blockquote>
END
);
